//
// Created by sanhuazhang on 2019/05/02
//

/*
 * Tencent is pleased to support the open source community by making
 * WCDB available.
 *
 * Copyright (C) 2017 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *       https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "WCTCancellationSignal.h"
#import "WCTCommon.h"

NS_ASSUME_NONNULL_BEGIN

/**
 This class is a wrapper for sqlite db handle of type `sqlite3*`
 @warning Not Thread-safe.
 */
WCDB_API @interface WCTHandle : NSObject

/**
 You should use `WCDB_GET_SCOPED_HANDLE` or `-[WCTDatabase getHandle]` to obtain WCTHandle.
 */
- (instancetype)init UNAVAILABLE_ATTRIBUTE;

#pragma mark - Handle
/**
 The database that generate current handle.
 */
@property (nonatomic, readonly) WCTDatabase *database;

/**
 @brief Recycle the sqlite db handle inside, and the current handle will no longer be able to perform other operations.
 @warning If you use obtain current handle through `-[WCTDatabase getHandle]`, you need to call its invalidate method when you are done with it, otherwise you will receive an Assert Failure.
 */
- (void)invalidate;

/**
 @brief Check whether the current handle still holds a sqlite db handle.
 @return YES if there is a valid sqlite db handle inside current handle.
 */
- (BOOL)isValidated;

/**
 @brief Try to get a new sqlite db handle if the current handle does not hold one inside.
 @return YES if current handle is validated.
 */
- (BOOL)validate;

#pragma mark - Statement

/**
 @brief Use `sqlite3_prepare` internally to prepare a new statement, and wrap the `sqlite3_stmt` generated by `sqlite3_prepare` into `WCTPreparedStatement` to return.
 If the statement has been prepared by this method before, and you have not used `-[WCTHandle invalidate]` or `-[WCTHandle finalizeAllStatements]` to finalize it, then you can use this method to regain the previously generated `sqlite3_stmt`.
 @note  This method is designed for the situation where you need to use multiple statements at the same time to do complex database operations. You can prepare a new statement without finalizing the previous statements, so that you can save the time of analyzing SQL syntax.
 If you only need to use one statement, or you no longer need to use the previous statements when you use a new statement, it is recommended to use `-[WCTHandle execute:]` or `-[WCTHandle prepare:]`.
 @see   `-[WCTHandle execute:]`
 @see   `-[WCTHandle prepare:]`
 @param statement The SQL statememt needed to prepare.
 @return a `WCTPreparedStatement` object with `sqlite3_stmt` inside, or nil if error occurs.
 */
- (WCTPreparedStatement *)getOrCreatePreparedStatement:(const WCDB::Statement &)statement;

/**
 @brief Use `sqlite3_finalize` to finalize all `sqlite3_stmt` generate by current handle.
 @note  `-[WCTHandle invalidate]` will internally call the current method.
 */
- (void)finalizeAllStatements;

#pragma mark - Execute
/**
 @brief Execute a statement directly.
 @warning You should firstly use `-[WCTHandle finalizeStatement]` to finalize the previous statement prepared by `-[WCTHandle prepare:]`.
 @return YES if no error occurs.
 */
- (BOOL)execute:(const WCDB::Statement &)statement;

/**
 @brief Execute a sql string directly.
 @warning You should no use this method to access or modify the data in a migrating table and you should firstly use `-[WCTHandle finalizeStatement]` to finalize the previous statement prepared by `-[WCTHandle prepare:]`.
 @see   `-[WCTDatabase addMigration:withFilter:]`
 @return YES if no error occurs.
 */
- (BOOL)rawExecute:(NSString *)sql;

#pragma mark - Prepare
/**
 @brief Use `sqlite3_prepare` internally to prepare a new statement.
 @warning You should firstly use `-[WCTHandle finalizeStatement]` to finalize the previous statement prepared by `-[WCTHandle prepare:]`. If you need to prepare multiple statements at the same time, `-[WCTHandle getOrCreatePreparedStatement:]` is recommended.
 @see   `-[WCTHandle getOrCreatePreparedStatement:]`
 @return YES if no error occurs.
 */
- (BOOL)prepare:(const WCDB::Statement &)statement;
/**
 @brief Use `sqlite3_prepare` internally to prepare a sql string.
 @warning You should no use this method to access or modify the data in a migrating table and you should firstly use `-[WCTHandle finalizeStatement]` to finalize the previous statement prepared by `-[WCTHandle prepare:]`.
 @see   `-[WCTDatabase addMigration:withFilter:]`
 @return YES if no error occurs.
 */
- (BOOL)rawPrepare:(NSString *)sql;

/**
 @brief Check if there is a statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return YES if there is a prepared statement.
 */
- (BOOL)isPrepared;

/**
 @brief Finalize the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @warning You should finalize the previously prepared statement before use a new statement.
 */
- (void)finalizeStatement;

#pragma mark - Step
/**
 @brief The wrapper of `sqlite3_step`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return YES if no error occurs.
 */
- (BOOL)step;

/**
 @brief The wrapper of `sqlite3_reset`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)reset;

/**
 @brief The wrapper of `sqlite3_clear_bindings`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)clearBindings;

/**
 @brief Check if you can continue stepping.
 @return YES if you can continue stepping.
 */
- (BOOL)done;

#pragma mark - State
/**
 @brief The wrapper of `sqlite3_stmt_readonly`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (long long)getLastInsertedRowID;

/**
 @brief The wrapper of `sqlite3_changes`.
 */
- (int)getChanges;

/**
 @brief The wrapper of `sqlite3_total_changes`.
 */
- (int)getTotalChanges;

/**
 @brief The wrapper of `sqlite3_stmt_readonly`.
 It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (BOOL)isStatementReadonly;

#pragma mark - Bind
/**
 @brief The wrapper of `sqlite3_bind_int64`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindInteger:(const int64_t &)value toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_double`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindDouble:(const double &)value toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_null`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindNullToIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_text`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindString:(NSString *)string toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_int64`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindData:(NSData *)data toIndex:(int)index;

/**
 @brief Bind an NSNumber.
 It will call `sqlite3_bind_int64` or `sqlite3_bind_double` according to the underlying data type of NSNumber.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindNumber:(NSNumber *)number toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_*`.
 It will call the appropriate routine according to the column type returned by +[WCTColumnCoding columnType].
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @param value WCTColumnCodingValue can be NSString, NSNumber, NSData, NSNull, nil and any NSObject that conforms to WCTColumnCoding.
 @see   `WCTColumnCoding`
 */
- (void)bindValue:(nullable WCTColumnCodingValue *)value
          toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_*` for binding property of object to index.
 It will call the appropriate routine according to the data type of property.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindProperty:(const WCTProperty &)property
            ofObject:(WCTObject *)object
             toIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_bind_*` for binding the specified properties of object.
 It will call the appropriate routine according to the data type of properties.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)bindProperties:(const WCTProperties &)properties
              ofObject:(WCTObject *)object;

/**
 @brief The wrapper of `sqlite3_bind_parameter_index`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (int)bindParameterIndex:(const WCDB::BindParameter &)parameter;

#pragma mark - Extract Column meta
/**
 @brief The wrapper of `sqlite3_column_count`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (int)extractNumberOfColumns;

/**
@brief The wrapper of `sqlite3_column_type`.
@note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
*/
- (WCTColumnType)extractTypeAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_origin_name`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSString *)extractOriginColumnNameAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_name`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSString *)extractColumnNameAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_table_name`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSString *)extractTableNameAtIndex:(int)index;

#pragma mark - Extract Row Data
/**
 @brief The wrapper of `sqlite3_column_int64`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (int64_t)extractIntegerAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_double`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (double)extractDoubleAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_text`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSString *)extractStringAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_int64` and `sqlite3_column_double`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSNumber *)extractNumberAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_blob`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (NSData *)extractDataAtIndex:(int)index;

/**
 @brief The wrapper of `sqlite3_column_*`.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return The real type of WCTValue depends on the value in database. It could be NSNumber, NSString, NSData or nil.
 */
- (nullable WCTValue *)extractValueAtIndex:(int)index;

/**
 @brief Extract all values of current row.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return An array of NSObject that conforms to WCTValue.
 */
- (WCTOneRow *)extractRow;

/**
 @brief Extract the values of the current row and assign them into the properties specified by resultColumns of a new WCTTableCoding object.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return A WCTTableCoding object.
 */
- (WCTObject *)extractObjectOnResultColumns:(const WCTResultColumns &)resultColumns;

/**
 @brief Extract the results of a multi-table query in the current row.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return A dictionary containing the result of multi-table. The keys of the dictionary are the table names, and their corresponding values are the results of these tables.
 */
- (WCTMultiObject *)extractMultiObjectOnResultColumns:(const WCTResultColumns &)resultColumns;

/**
 @brief Extract value at index and set it to the corresponding property of object.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 */
- (void)extractValueAtIndex:(int)index
                 toProperty:(const WCTProperty &)property
                   ofObject:(WCTObject *)object;

#pragma mark - Step And Extract All Data
/**
 @brief Extract all the values of the first column in the result.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return An array of NSObject that conforms to WCTValue, or nil if error occurs.
 */
- (nullable WCTOneColumn *)allValues;

/**
 @brief Extract all the values of the column at index in the result.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return An array of NSObject that conforms to WCTValue, or nil if error occurs.
 */
- (nullable WCTOneColumn *)allValuesAtIndex:(int)index;

/**
 @brief Extract all the values in the result.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return A two-dimensional array, or nil if error occurs.
 */
- (nullable WCTColumnsXRows *)allRows;

/**
 @brief Extract the values of all rows in the result and assign them into the properties specified by resultColumns of new WCTTableCoding objects.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return An array of WCTTableCoding object, or nil if error occurs.
 */
- (nullable NSArray /* <WCTObject*> */ *)allObjectsOnResultColumns:(const WCTResultColumns &)resultColumns;

/**
 @brief Extract the results of a multi-table query.
 @note It is for the statement previously prepared by `-[WCTHandle prepare:]` or `-[WCTHandle rawPrepare:]`.
 @return An array of WCTMultiObject, or nil if error occurs.
 */
- (nullable NSArray<WCTMultiObject *> *)allMultiObjectsOnResultColumns:(const WCTResultColumns &)resultColumns;

#pragma mark - Cancellation signal
/**
 @brief The wrapper of `sqlite3_progress_handler`.
 
 You can asynchronously cancel all operations on the current handle through `WCTCancellationSignal`.
 
        WCTCancellationSignal *signal = [[WCTCancellationSignal alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            WCTHandle* handle = [database getHandle];
            [handle attachCancellationSignal:signal];
 
            // Do some time-consuming database operations.
 
            [handle detachCancellationSignal];
        });
        [signal cancel];
 
 @warning Note that you can use `WCTCancellationSignal` in multiple threads,
 but you can only use the current handle in the thread that you got it.
 */
- (void)attachCancellationSignal:(WCTCancellationSignal *)signal;

/**
 @brief Detach the attached `WCTCancellationSignal`.
        `WCTCancellationSignal` can be automatically detached when the current handle deconstruct.
 */
- (void)detachCancellationSignal;

#pragma mark - Error
/**
 @brief Get the most recent error for current handle in the current thread.
        Since it is too cumbersome to get the error after every operation, it‘s better to use monitoring interfaces to obtain errors and print them to the log.
 @see   `[WCTDatabase globalTraceError:]`
 @see   `[WCTDatabase traceError:]`
 
 @return WCTError
 */
- (WCTError *)error;

@end

NS_ASSUME_NONNULL_END
